package org.zh.client.tra_aop;


import com.alibaba.druid.util.JdbcUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.zh.client.config.GlobalStatic.*;
import static org.zh.util.CommonStatic.om;
import static org.zh.util.CommonStatic.smartHttpPUT;

@Service
public class GlobalTransactionService {

    private final SQLiteJDBC sqLiteJDBC;
    private final DataSource dataSource;

    public GlobalTransactionService(DataSource dataSource, SQLiteJDBC sqLiteJDBC) {
        this.sqLiteJDBC = sqLiteJDBC;
        this.dataSource = dataSource;
    }

    public void putGlobalError(RemoteTransactionBean error) {
        synchronized (globalErrorCache) {
            if (globalErrorCache.containsKey(error.getGlobal_TX_ID())) {
                globalErrorCache.get(error.getGlobal_TX_ID()).add(error);
            } else {
                List<RemoteTransactionBean> list = new ArrayList<>(1);
                list.add(error);
                globalErrorCache.put(error.getGlobal_TX_ID(), list);
            }
            sqLiteJDBC.insertRemote(error.getGlobal_TX_ID(), error.getROOT_URL(), error.isCanRollback(), error.getNodeURI(), error.getNodeStatus());
        }
        globalRegistryCache.get(error.getGlobal_TX_ID()).stream().filter(rtb -> rtb.getNodeStatus() == NodeStatus.end.value() && rtb.isCanRollback())
                .forEach(rb -> Thread.startVirtualThread(() -> {
                    try {
                        HttpResponse<String> response = smartHttpPUT(URI.create(rb.getNodeURI() + GLOBAL_ROLLBACK_COMMIT), HttpRequest.BodyPublishers.ofString(om.writeValueAsString(rb)), HttpResponse.BodyHandlers.ofString());
                        if (response.statusCode() != 200) {
                            throw new RuntimeException("全局发生错误 业务节点:::" + rb.getNodeURI() + "远程http回滚失败");
                        }
                    } catch (JsonProcessingException e) {
                        throw new RuntimeException(e);
                    }
                }));
    }

    public void putGlobalRegistry(RemoteTransactionBean rtb) {
        if (globalErrorCache.containsKey(rtb.getGlobal_TX_ID())) {
            if (rtb.isCanRollback()) {
                rtb.setCommit(false);
                Thread.startVirtualThread(() -> {
                    try {
                        HttpResponse<String> response = smartHttpPUT(URI.create(rtb.getNodeURI() + GLOBAL_ROLLBACK_COMMIT), HttpRequest.BodyPublishers.ofString(om.writeValueAsString(rtb)), HttpResponse.BodyHandlers.ofString());
                        if (response.statusCode() != 200) {
                            throw new RuntimeException("全局发生错误 业务节点:::" + rtb.getNodeURI() + "回滚失败");
                        }
                    } catch (JsonProcessingException e) {
                        throw new RuntimeException(e);
                    }
                });
            }
        } else {
            synchronized (globalRegistryCache) {
                if (globalRegistryCache.containsKey(rtb.getGlobal_TX_ID())) {
                    globalRegistryCache.get(rtb.getGlobal_TX_ID()).add(rtb);
                } else {
                    List<RemoteTransactionBean> list = new ArrayList<>(3);
                    list.add(rtb);
                    globalRegistryCache.put(rtb.getGlobal_TX_ID(), list);
                }
                sqLiteJDBC.insertRemote(rtb.getGlobal_TX_ID(), rtb.getROOT_URL(), rtb.isCanRollback(), rtb.getNodeURI(), rtb.getNodeStatus());
            }
        }
    }


    public void commitOrRollback(String Global_TX_ID, boolean isCommit) {
        LocalTransaction lt = LocalTransactionCache.get(Global_TX_ID);
        Connection conn = null;
        try {
            if (lt != null) {
                if (lt.type().equals(GlobalType.TX)) {
                    conn = lt.conn();
                    if (isCommit) {
                        conn.commit();
                        sqLiteJDBC.updateTX(Global_TX_ID, 1);
                    } else {
                        conn.rollback();
                        sqLiteJDBC.updateTX(Global_TX_ID, 2);
                    }
                    conn.setAutoCommit(true);
                    conn.close();
                } else if (lt.type().equals(GlobalType.TA)) {
                    if (!isCommit) {
                        rollback(lt);
                        sqLiteJDBC.updateTA(Global_TX_ID, 2);
                    } else {
                        sqLiteJDBC.updateTA(Global_TX_ID, 1);
                    }
                }
            }
            LocalTransactionCache.remove(Global_TX_ID);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            JdbcUtils.close(conn);
        }
    }


    private void rollback(LocalTransaction lt) {
        Object[] sqlparams = null;
        String parsedSql = "";
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            if (lt.params() instanceof Map) {
                Pattern pattern = Pattern.compile(":([a-zA-Z_][a-zA-Z0-9_]*)");
                Matcher matcher = pattern.matcher(lt.taSql());
                List<Object> paramNames = new ArrayList<>();
                StringBuilder sb = new StringBuilder();
                while (matcher.find()) {
                    paramNames.add(((Map<String, Object>) lt.params()).get(matcher.group(1)));
                    matcher.appendReplacement(sb, "?");
                }
                matcher.appendTail(sb);
                parsedSql = sb.toString();
                sqlparams = paramNames.toArray();
            }
            if (lt.params() instanceof Object[]) {
                parsedSql = lt.taSql();
                sqlparams = (Object[]) lt.params();
            }
            if (lt.params() instanceof Collection<?>) {
                parsedSql = lt.taSql();
                sqlparams = ((Collection<?>) lt.params()).toArray();
            }
            conn = dataSource.getConnection();
            ps = conn.prepareStatement(parsedSql);
            conn.setAutoCommit(false);
            if (sqlparams != null) {
                for (int i = 0; i < sqlparams.length; i++) {
                    ps.setObject(i + 1, sqlparams[i]);
                }
            }
            ps.executeUpdate();
            ps.close();
            conn.commit();
            conn.setAutoCommit(true);
            conn.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            JdbcUtils.close(conn);
            JdbcUtils.close(ps);
        }

    }

}
